package mspbots.next.ticket.core.client;


import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.log4j.Log4j2;
import mspbots.next.ticket.annotation.ApiException;
import mspbots.next.ticket.common.QueryParams;
import mspbots.next.ticket.common.RequestApiParams;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkRelation;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;

/**
 * com.spots.web.CoreClient
 *
 * @author Alex bob(https://github.com/vnobo)
 * @date Created by 2019/8/26
 */
@Log4j2
@Service
public class WiseClient {

    private final WebClient webClient;

    public WiseClient(WebClient.Builder builder,
                      ReactorLoadBalancerExchangeFilterFunction lbFunction) {
        this.webClient = builder.baseUrl("http://connect-wise-client")
                .filter(lbFunction)
                .build();
    }

    public Mono<JsonNode> get(String path, MultiValueMap<String, String> queryParams, Map<String, Object> pathParams) {
        log.debug("Request path: {},queryParams: {},pathParams: {}", path, queryParams, pathParams);
        return this.webClient.get()
                .uri(uriBuilder -> uriBuilder.path(path).queryParams(queryParams).build(pathParams))
                .retrieve()
                .onStatus(HttpStatus::isError, clientResponse -> clientResponse.createException()
                        .flatMap(errNode -> Mono.error(ApiException.withMsg(clientResponse.statusCode(),
                                errNode.getResponseBodyAsString()))))
                .bodyToMono(JsonNode.class);
    }

    public Flux<JsonNode> fetch(RequestApiParams apiParams) {
        return this.request(apiParams)
                .expand(responseEntity -> {
                    List<String> links = responseEntity.getHeaders().getValuesAsList(HttpHeaders.LINK);
                    String newUrl = links.parallelStream().map(Link::valueOf)
                            .filter(link -> link.hasRel(LinkRelation.of("next")))
                            .map(link -> URLDecoder.decode(link.getHref(), Charset.defaultCharset()))
                            .findAny().orElse(null);

                    if (ObjectUtils.isEmpty(newUrl)) {
                        return Mono.empty();
                    }
                    log.debug("===Request next url [{}]===", newUrl);
                    return this.request(RequestApiParams.withDefault(apiParams.getTenantId())
                            .request("/client/{tenantId}/api")
                            .queryParams(new QueryParams(1).href(newUrl)));
                })
                .flatMap(responseEntity -> {
                    if (ObjectUtils.isEmpty(responseEntity.getBody())) {
                        return Flux.empty();
                    }
                    return Flux.fromIterable(responseEntity.getBody());
                });
    }

    private Mono<ResponseEntity<JsonNode>> request(RequestApiParams requestApiParams) {
        log.debug("Request path: {},queryParams: {}",
                requestApiParams.getRequest(), requestApiParams.getQueryParams());
        return this.webClient.get()
                .uri(uriBuilder -> uriBuilder.path(requestApiParams.getRequest())
                        .queryParams(requestApiParams.getQueryParams())
                        .build(Map.of("tenantId", requestApiParams.getTenantId())))
                .exchangeToMono(clientResponse -> {
                    if (clientResponse.statusCode().isError()) {
                        return clientResponse.createException()
                                .flatMap(err -> Mono.error(ApiException.withMsg(clientResponse.statusCode(),
                                        err.getResponseBodyAsString())));
                    }
                    return clientResponse.toEntity(JsonNode.class);
                });
    }
}
